package com.chenshu.doc_searchengine.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created with IntelliJ IDEA
 * Description：构建索引的入口
 * User：chenshu
 * Date：2024-05-02
 * Time：20:30
 */
@Slf4j
public class Parser {
    private final Index index = new Index();
    private static final String INPUT_PATH = "/Users/chenshu/Code/docs/api";

    private AtomicLong parseContentTime = new AtomicLong(0);
    private AtomicLong addDocTime = new AtomicLong(0);

    /**
     * Parser类的入口（单线程）
     * 根据上面的路径枚举出所有文件(html)
     */
    public void run() {
        long begin = System.currentTimeMillis();
        log.info("枚举文件开始！");
        //1. 枚举出该路径中的所有文件
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH, fileList);
        long endEnumFile = System.currentTimeMillis();
        log.info("枚举文件结束，消耗时间：" + (endEnumFile - begin) + "ms");
        //2. 针对上面枚举路径，打开文件，读取文件内容，进行解析，并构建索引
        log.info("制作索引开始！");
        for (File f : fileList) {
            System.out.println("开始解析：" + f.getAbsolutePath());
            parseHTML(f);
        }
        long end = System.currentTimeMillis();
        log.info("索引制作结束, 消耗时间：" + (end - endEnumFile) + "ms");
        //3. 把内存中构建好的索引数据结构，保存到指定文件中
        index.save();
    }

    /**
     * 引入线程池制作索引提高效率
     */
    public void runByThread() {
        long begin = System.currentTimeMillis();
        log.info("枚举文件开始！");
        //1. 枚举出该路径中的所有文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH, files);
        long endEnumFile = System.currentTimeMillis();
        log.info("枚举文件结束，消耗时间：" + (endEnumFile - begin) + "ms");

        log.info("制作索引开始！");
        //引入线程池
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        //2. 针对上面枚举路径，打开文件，读取文件内容，进行解析，并构建索引
        CountDownLatch latch = new CountDownLatch(files.size());
        for (File f : files) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    parseHTML(f);
                    latch.countDown();
                }
            });
        }
        //3. 把内存中构建好的索引数据结构，保存到指定文件中
        try {
            latch.await();
            executorService.shutdown();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        log.info("索引制作结束, 消耗时间：" + (end - endEnumFile) + "ms");
        log.info("制作索引过程中，parseContent消耗时间：" + parseContentTime);
        log.info("制作索引过程中，addDoc消耗时间：" + addDocTime);
        index.save();
    }

    private void parseHTML(File f) {
        //1. 解析出HTML的标题
        String title = parseTitle(f);
        //2. 解析出HTML对应的URL
        String url = parseUrl(f);
        //3. 解析出HTML对应的正文(为了获取描述)
        long beg = System.nanoTime();
        String content = parseContentByRegex(f);
        long mid = System.nanoTime();
        //4. 把解析出来的信息，加入索引中
        index.addDoc(title, url, content);
        long end = System.nanoTime();

        parseContentTime.addAndGet(mid - beg);
        addDocTime.addAndGet(end - mid);
    }

    /**
     * 解析内容
     * @param f
     * @return
     */
    public String parseContent(File f) {
        //设置一个1M的预读缓冲区
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f), 1024*1024)){
            boolean copyFlag = true;
            StringBuilder content = new StringBuilder();
            while (true) {
                // 读到末尾就会返回-1
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;
                }
                char c = (char) ret;
                if (copyFlag) {
                    //读到'<'就关闭开关
                    if (c == '<') {
                        copyFlag = false;
                        //跳过本次循环
                        continue;
                    }
                    //替换换行为空格
                    if (c == '\n' || c =='\r') {
                        c = ' ';
                    }
                    content.append(c);
                } else {
                    //读到'>'就关闭开关
                    if (c == '>') {
                        copyFlag = true;
                    }
                }
            }
            return content.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String readFile(File f) {
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f))) {
            StringBuilder content = new StringBuilder();
            while (true) {
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;
                }
                char c = (char) ret;
                if (c == '\n' || c == '\r') {
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 基于正则表达式解析内容
     * @param f
     * @return
     */
    public String parseContentByRegex(File f) {
        //1. 先把整个文件都读到String里面
        String content = readFile(f);
        //2. 替换掉script标签
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");
        //3. 替换掉css样式
        content = content.replaceAll("<style>(.*?)</style>", " ");
        //4. 替换掉普通html标签
        content = content.replaceAll("<.*?>", " ");
        //5. 替换多余的空格
        content = content.replaceAll("\\s+", " ");

        return content;
    }

    private String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/javase/8/docs/api";

        String localUrl = f.getAbsolutePath();
        String part2 = localUrl.substring(INPUT_PATH.length());

        return part1 + part2;
    }

    private String parseTitle(File f) {
        String name =  f.getName();
        return name.substring(0, name.length() - ".html".length());
    }

    /**
     *
     * @param inputPath 从哪个目录进行递归遍历
     * @param fileList 递归得到的结果
     */
    private void enumFile(String inputPath, ArrayList<File> fileList) {
        File rootPath = new File(inputPath);
        File[] files = rootPath.listFiles();
        if (files == null) {
            return;
        }
        for (File f : files) {
            if (f.isDirectory()) {
                enumFile(f.getAbsolutePath(), fileList);
            } else {
                if (f.getAbsolutePath().endsWith(".html")) {
                    fileList.add(f);
                }
            }
        }
    }

    public static void main(String[] args) {
        Parser parser = new Parser();
        parser.runByThread();
    }
}
